# 导入asyncio模块，用于异步编程
import asyncio

# 导入aiohttp模块，用于异步HTTP请求
import aiohttp

# 导入urljoin和urlparse方法，用于处理URL
from urllib.parse import urljoin, urlparse

# 导入time模块，用于计时
import time


# 定义异步Web爬虫类
class AsyncWebCrawler:
    # 类文档字符串，说明用途
    """异步Web爬虫"""

    # 初始化方法，设置最大并发数和延迟时间
    def __init__(self, max_concurrent=10, delay=1):
        # 最大并发任务数
        self.max_concurrent = max_concurrent

        # 每次请求的延迟(秒)
        self.delay = delay

        # 已访问的URL集合
        self.visited = set()

        # 创建异步信号量控制并发数,信号量允许"最多N个"协程同时访问某一资源，适用于连接池、限流器等场景
        self.semaphore = asyncio.Semaphore(max_concurrent)

        # HTTP会话对象
        self.session = None

    # 爬虫的主入口，异步方法
    async def crawl(self, start_url, max_pages=20):
        # 文档字符串：开始爬取
        """开始爬取"""
        # 创建aiohttp的会话
        self.session = aiohttp.ClientSession()

        # 创建异步队列
        queue = asyncio.Queue()

        print(f"开始爬取: {start_url}")

        # 将开始URL放入队列
        await queue.put(start_url)

        # 创建多个worker协程任务
        workers = [
            asyncio.create_task(self.worker(f"worker-{i}", queue, max_pages))
            for i in range(self.max_concurrent)
        ]

        try:
            # 等待队列消费完毕
            await queue.join()

        finally:
            # 取消所有worker任务
            for worker in workers:
                worker.cancel()
            # 关闭HTTP会话
            await self.session.close()

        # 输出总共访问页面数量
        print(f"爬取完成! 总共访问了 {len(self.visited)} 个页面")
        # 返回访问过的页面列表
        return list(self.visited)

    # worker工作线程，异步方法
    async def worker(self, name, queue, max_pages):
        # 文档字符串：爬虫工作线程
        """爬虫工作线程"""
        # 当未达到最大页面数时循环工作
        while len(self.visited) < max_pages:
            try:
                # 尝试从队列获取新的URL，设置超时时间为5秒
                url = await asyncio.wait_for(queue.get(), timeout=5.0)

                # 如果URL已经访问过，则标记完成后继续下一个
                if url in self.visited:
                    queue.task_done()
                    continue

                # 使用信号量限制并发请求
                async with self.semaphore:
                    # 处理单个页面
                    await self.process_page(url, queue)

                # 将当前URL添加到已访问集合
                self.visited.add(url)
                # 通知队列该任务完成
                queue.task_done()

                # 延迟，避免过快请求
                await asyncio.sleep(self.delay)

            # 如果队列空闲超过超时时间，退出
            except asyncio.TimeoutError:
                break
            # 其他异常输出错误信息并继续
            except Exception as e:
                print(f"{name} 处理错误: {e}")
                queue.task_done()

    # 处理单个页面的方法
    async def process_page(self, url, queue):
        # 文档字符串：处理单个页面
        """处理单个页面"""
        try:
            # 发送HTTP GET请求，设置超时为10秒
            async with self.session.get(url, timeout=10) as response:
                # 如果响应状态为200
                if response.status == 200:
                    # 获取页面内容
                    content = await response.text()
                    # 输出爬取成功消息和内容长度
                    print(f"成功爬取: {url} (长度: {len(content)})")

                    # 可以在这里解析页面并提取更多链接
                    # 简化版：只做演示用途
                    # 只在已访问数量小于10时添加新链接
                    if len(self.visited) < 10:  # 限制链接数量
                        # 提取页面中的链接
                        new_links = self.extract_links(content, url)
                        # 遍历新链接，将未访问过的加入队列
                        for link in new_links:
                            if link not in self.visited:
                                await queue.put(link)

        # 捕获并输出请求异常
        except Exception as e:
            print(f"爬取失败 {url}: {e}")

    # 简单的链接提取方法
    def extract_links(self, content, base_url):
        # 文档字符串：提取链接（简化版）
        """提取链接（简化版）"""
        # 实际项目中通常使用BeautifulSoup等解析HTML
        # 这里仅作示例，返回模拟链接
        return [f"{base_url}/page1", f"{base_url}/page2", f"{base_url}/page3"]


# 定义异步函数，用于演示Web爬虫使用
async def web_crawler():
    # 文档字符串：Web爬虫演示
    """Web爬虫演示"""
    # 创建爬虫对象，设置最大并发数和延迟
    crawler = AsyncWebCrawler(max_concurrent=5, delay=0.5)

    # 记录开始时间
    start_time = time.time()
    # 执行爬虫，爬取最多10个页面
    results = await crawler.crawl("https://httpbin.org/html", max_pages=10)
    # 记录结束时间
    end_time = time.time()

    # 输出爬虫运行耗时
    print(f"爬虫耗时: {end_time - start_time:.2f}秒")
    # 输出访问的页面列表
    print(f"访问的页面: {results}")


# 运行Web爬虫演示异步函数
asyncio.run(web_crawler())
